 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Brad Reynolds - bug 168153
  *******************************************************************************/

 package org.eclipse.core.databinding.observable;

 import java.util.LinkedList ;

 import org.eclipse.core.databinding.Binding;
 import org.eclipse.core.databinding.util.Policy;
 import org.eclipse.core.runtime.ISafeRunnable;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.SafeRunner;
 import org.eclipse.core.runtime.Status;

 /**
  * A realm defines a context from which objects implementing {@link IObservable}
  * must be accessed, and on which these objects will notify their listeners. To
  * bridge between observables from different realms, subclasses of
  * {@link Binding} can be used.
  * <p>
  * A block of code is said to be executing within a realm if calling
  * {@link #isCurrent()} from that block returns true. Code reached by calling
  * methods from that block will execute within the same realm, with the
  * exception of methods on this class that can be used to execute code within a
  * specific realm. Clients can use {@link #syncExec(Runnable)},
  * {@link #asyncExec(Runnable)}, or {@link #exec(Runnable)} to execute a
  * runnable within this realm. Note that using {@link #syncExec(Runnable)} can
  * lead to deadlocks and should be avoided if the current thread holds any
  * locks.
  * </p>
  * <p>
  * It is instructive to think about possible implementations of Realm: It can be
  * based on executing on a designated thread such as a UI thread, or based on
  * holding a lock. In the former case, calling syncExec on a realm that is not
  * the current realm will execute the given runnable on a different thread (the
  * designated thread). In the latter case, calling syncExec may execute the
  * given runnable on the calling thread, but calling
  * {@link #asyncExec(Runnable)} will execute the given runnable on a different
  * thread. Therefore, no assumptions can be made about the thread that will
  * execute arguments to {@link #asyncExec(Runnable)},
  * {@link #syncExec(Runnable)}, or {@link #exec(Runnable)}.
  * </p>
  * <p>
  * It is possible that a block of code is executing within more than one realm.
  * This can happen for implementations of Realm that are based on holding a lock
  * but don't use a separate thread to run runnables given to
  * {@link #syncExec(Runnable)}. Realm implementations of this kind should be
  * appropriately documented because it increases the opportunity for deadlock.
  * </p>
  * <p>
  * Some implementations of {@link IObservable} provide constructors which do not
  * take a Realm argument and are specified to create the observable instance
  * with the current default realm. The default realm can be set for the
  * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}.
  * Note that the default realm does not have to be the current realm.
  * </p>
  * <p>
  * Subclasses must override at least one of asyncExec()/syncExec(). For realms
  * based on a designated thread, it may be easier to implement asyncExec and
  * keep the default implementation of syncExec. For realms based on holding a
  * lock, it may be easier to implement syncExec and keep the default
  * implementation of asyncExec.
  * </p>
  *
  * @since 1.0
  *
  * @see IObservable
  */
 public abstract class Realm {

     private static ThreadLocal defaultRealm = new ThreadLocal ();

     /**
      * Returns the default realm for the calling thread, or <code>null</code>
      * if no default realm has been set.
      *
      * @return the default realm, or <code>null</code>
      */
     public static Realm getDefault() {
         return (Realm) defaultRealm.get();
     }
     
     /**
      * Sets the default realm for the calling thread, returning the current
      * default thread. This method is inherently unsafe, it is recommended to
      * use {@link #runWithDefault(Realm, Runnable)} instead. This method is
      * exposed to subclasses to facilitate testing.
      *
      * @param realm
      * the new default realm, or <code>null</code>
      * @return the previous default realm, or <code>null</code>
      */
     protected static Realm setDefault(Realm realm) {
         Realm oldValue = getDefault();
         defaultRealm.set(realm);
         return oldValue;
     }

     /**
      * @return true if the caller is executing in this realm. This method must
      * not have side-effects (such as, for example, implicitly placing
      * the caller in this realm).
      */
     abstract public boolean isCurrent();

     private Thread workerThread;

     LinkedList workQueue = new LinkedList ();
     
     /**
      * Runs the given runnable. If an exception occurs within the runnable, it
      * is logged and not re-thrown. If the runnable implements
      * {@link ISafeRunnable}, the exception is passed to its
      * <code>handleException<code> method.
      *
      * @param runnable
      */
     protected static void safeRun(final Runnable runnable) {
         ISafeRunnable safeRunnable;
         if (runnable instanceof ISafeRunnable) {
             safeRunnable = (ISafeRunnable) runnable;
         } else {
             safeRunnable = new ISafeRunnable() {
                 public void handleException(Throwable exception) {
                     Policy
                             .getLog()
                             .log(
                                     new Status(
                                             IStatus.ERROR,
                                             Policy.JFACE_DATABINDING,
                                             IStatus.OK,
                                             "Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$
 }
                 public void run() throws Exception {
                     runnable.run();
                 }
             };
         }
         SafeRunner.run(safeRunnable);
     }

     /**
      * Causes the <code>run()</code> method of the runnable to be invoked from
      * within this realm. If the caller is executing in this realm, the
      * runnable's run method is invoked directly, otherwise it is run at the
      * next reasonable opportunity using asyncExec.
      * <p>
      * If the given runnable is an instance of {@link ISafeRunnable}, its
      * exception handler method will be called if any exceptions occur while
      * running it. Otherwise, the exception will be logged.
      * </p>
      *
      * @param runnable
      */
     public void exec(Runnable runnable) {
         if (isCurrent()) {
             safeRun(runnable);
         } else {
             asyncExec(runnable);
         }
     }

     /**
      * Causes the <code>run()</code> method of the runnable to be invoked from
      * within this realm at the next reasonable opportunity. The caller of this
      * method continues to run in parallel, and is not notified when the
      * runnable has completed.
      * <p>
      * If the given runnable is an instance of {@link ISafeRunnable}, its
      * exception handler method will be called if any exceptions occur while
      * running it. Otherwise, the exception will be logged.
      * </p>
      * <p>
      * Subclasses should use {@link #safeRun(Runnable)} to run the runnable.
      * </p>
      *
      * @param runnable
      */
     public void asyncExec(Runnable runnable) {
         synchronized (workQueue) {
             ensureWorkerThreadIsRunning();
             workQueue.addLast(runnable);
             workQueue.notifyAll();
         }
     }

     /**
      *
      */
     private void ensureWorkerThreadIsRunning() {
         if (workerThread == null) {
             workerThread = new Thread () {
                 public void run() {
                     try {
                         while (true) {
                             Runnable work = null;
                             synchronized (workQueue) {
                                 while (workQueue.isEmpty()) {
                                     workQueue.wait();
                                 }
                                 work = (Runnable ) workQueue.removeFirst();
                             }
                             syncExec(work);
                         }
                     } catch (InterruptedException e) {
                         // exit
 }
                 }
             };
             workerThread.start();
         }
     }

     /**
      * Causes the <code>run()</code> method of the runnable to be invoked from
      * within this realm at the next reasonable opportunity. This method is
      * blocking the caller until the runnable completes.
      * <p>
      * If the given runnable is an instance of {@link ISafeRunnable}, its
      * exception handler method will be called if any exceptions occur while
      * running it. Otherwise, the exception will be logged.
      * </p>
      * <p>
      * Subclasses should use {@link #safeRun(Runnable)} to run the runnable.
      * </p>
      * <p>
      * Note: This class is not meant to be called by clients and therefore has
      * only protected access.
      * </p>
      *
      * @param runnable
      */
     protected void syncExec(Runnable runnable) {
         SyncRunnable syncRunnable = new SyncRunnable(runnable);
         asyncExec(syncRunnable);
         synchronized (syncRunnable) {
             while (!syncRunnable.hasRun) {
                 try {
                     syncRunnable.wait();
                 } catch (InterruptedException e) {
                     Thread.currentThread().interrupt();
                 }
             }
         }
     }

     static class SyncRunnable implements Runnable {
         boolean hasRun = false;

         private Runnable runnable;

         SyncRunnable(Runnable runnable) {
             this.runnable = runnable;
         }

         public void run() {
             try {
                 safeRun(runnable);
             } finally {
                 synchronized (this) {
                     hasRun = true;
                     this.notifyAll();
                 }
             }
         }
     }

     /**
      * Sets the provided <code>realm</code> as the default for the duration of
      * {@link Runnable#run()} and resets the previous realm after completion.
      * Note that this will not set the given realm as the current realm.
      *
      * @param realm
      * @param runnable
      */
     public static void runWithDefault(Realm realm, Runnable runnable) {
         Realm oldRealm = Realm.getDefault();
         try {
             defaultRealm.set(realm);
             runnable.run();
         } finally {
             defaultRealm.set(oldRealm);
         }
     }
 }

